﻿//
// (C) Copyright 2009 Irantha Suwandarathna (iranthas@hotmail.com)
// All rights reserved.
//

/* Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EffiProz.Core.Lib;
using EffiProz.Core.DataTypes;
using EffiProz.Core.Indexes;
using EffiProz.Core.Errors;
using EffiProz.Core.Persist;

namespace EffiProz.Core
{
    public class ParserDML : ParserDQL
    {
        public ParserDML(Session session, Scanner t)
            : base(session, t)
        {

        }

        /**
         * Retrieves an INSERT Statement from this parse context.
         */
        public StatementDMQL compileInsertStatement(RangeVariable[] outerRanges)
        {

            read();
            readThis(Tokens.INTO);

            bool[] columnCheckList;
            int[] columnMap;
            int colCount;
            Table table = readTableName();
            bool overridingUser = false;
            bool overridingSystem = false;
            bool assignsToIdentity = false;

            columnCheckList = null;
            columnMap = table.getColumnMap();
            colCount = table.getColumnCount();

            int position = getPosition();

            if (!table.isInsertable())
            {
                throw Error.error(ErrorCode.X_42545);
            }

            Table baseTable = table.getBaseTable();

            switch (token.tokenType)
            {

                case Tokens.DEFAULT:
                    {
                        read();
                        readThis(Tokens.VALUES);

                        Expression insertExpression = new Expression(OpTypes.ROW,
                            new Expression[] { });

                        insertExpression = new Expression(OpTypes.TABLE,
                                                          new Expression[]{
                                                      insertExpression });
                        columnCheckList = table.getNewColumnCheckList();

                        for (int i = 0; i < table.colDefaults.Length; i++)
                        {
                            if (table.colDefaults[i] == null
                                    && table.identityColumn != i)
                            {
                                if (!table.getColumn(i).isGenerated())
                                {
                                    throw Error.error(ErrorCode.X_42544);
                                }
                            }
                        }

                        StatementDMQL cs = new StatementInsert(session, table,
                                                               columnMap,
                                                               insertExpression,
                                                               columnCheckList,
                                                               compileContext);

                        return cs;
                    }
                case Tokens.OPENBRACKET:
                    {
                        int brackets = readOpenBrackets();

                        if (brackets == 1)
                        {
                            bool isQuery = false;

                            switch (token.tokenType)
                            {

                                case Tokens.WITH:
                                case Tokens.SELECT:
                                case Tokens.TABLE:
                                    {
                                        rewind(position);

                                        isQuery = true;

                                        break;
                                    }
                                default:
                                    break;
                            }

                            if (isQuery)
                            {
                                break;
                            }

                            OrderedHashSet columnNames = new OrderedHashSet();

                            readSimpleColumnNames(columnNames, table);
                            readThis(Tokens.CLOSEBRACKET);

                            colCount = columnNames.size();
                            columnMap = table.getColumnIndexes(columnNames);

                            if (token.tokenType != Tokens.VALUES
                                    && token.tokenType != Tokens.OVERRIDING)
                            {
                                break;
                            }

                            // fall through
                        }
                        else
                        {
                            rewind(position);

                            break;
                        }
                    }
                    goto case Tokens.OVERRIDING;
                // fall through
                case Tokens.OVERRIDING:
                    {
                        if (token.tokenType == Tokens.OVERRIDING)
                        {
                            read();

                            if (token.tokenType == Tokens.USER)
                            {
                                read();

                                overridingUser = true;
                            }
                            else if (token.tokenType == Tokens.SYSTEM)
                            {
                                read();

                                overridingSystem = true;
                            }
                            else
                            {
                                unexpectedToken();
                            }
                        }

                        if (token.tokenType != Tokens.VALUES)
                        {
                            break;
                        }
                    }
                    goto case Tokens.VALUES;
                // fall through
                case Tokens.VALUES:
                    {
                        read();

                        columnCheckList = table.getColumnCheckList(columnMap);

                        Expression insertExpressions =
                            XreadContextuallyTypedTable(colCount);
                        EfzList unresolved =
                            insertExpressions.resolveColumnReferences(outerRanges,
                                null);

                        ExpressionColumn.checkColumnsResolved(unresolved);
                        insertExpressions.resolveTypes(session, null);
                        setParameterTypes(insertExpressions, table, columnMap);

                        if (table != baseTable)
                        {
                            int[] baseColumnMap = table.getBaseTableColumnMap();
                            int[] newColumnMap = new int[columnMap.Length];

                            ArrayUtil.projectRow(baseColumnMap, columnMap,
                                                 newColumnMap);

                            columnMap = newColumnMap;
                        }

                        Expression[] rowList = insertExpressions.nodes;

                        for (int j = 0; j < rowList.Length; j++)
                        {
                            Expression[] rowArgs = rowList[j].nodes;

                            for (int i = 0; i < rowArgs.Length; i++)
                            {
                                Expression e = rowArgs[i];
                                ColumnSchema column =
                                    baseTable.getColumn(columnMap[i]);

                                if (column.isIdentity())
                                {
                                    assignsToIdentity = true;

                                    if (e.getType() != OpTypes.DEFAULT)
                                    {
                                        if (table.identitySequence.isAlways())
                                        {
                                            if (!overridingUser && !overridingSystem)
                                            {
                                                throw Error.error(ErrorCode.X_42543);
                                            }
                                        }
                                        else
                                        {
                                            /*
                                                                                if (overridingUser) {
                                                                                    throw Trace.error(
                                                                                        Trace.SQL_DEFAULT_CLAUSE_REQUITED);
                                                                                }
                                            */
                                        }
                                    }
                                }
                                else if (column.hasDefault()) { }
                                else if (column.isGenerated())
                                {
                                    if (e.getType() != OpTypes.DEFAULT)
                                    {
                                        throw Error.error(ErrorCode.X_42541);
                                    }
                                }
                                else
                                {
                                    if (e.getType() == OpTypes.DEFAULT)
                                    {
                                        throw Error.error(ErrorCode.X_42544);
                                    }
                                }

                                if (e.isParam())
                                {
                                    e.setAttributesAsColumn(column, true);
                                }
                            }
                        }

                        if (!assignsToIdentity
                                && (overridingUser || overridingSystem))
                        {
                            unexpectedTokenRequire(Tokens.T_OVERRIDING);
                        }

                        StatementDMQL cs = new StatementInsert(session, table,
                                                               columnMap,
                                                               insertExpressions,
                                                               columnCheckList,
                                                               compileContext);

                        return cs;
                    }
                case Tokens.WITH:
                case Tokens.SELECT:
                case Tokens.TABLE:
                    {
                        break;
                    }
                default:
                    {
                        throw unexpectedToken();
                    }
            }

            columnCheckList = table.getColumnCheckList(columnMap);

            if (table != baseTable)
            {
                int[] baseColumnMap = table.getBaseTableColumnMap();
                int[] newColumnMap = new int[columnMap.Length];

                ArrayUtil.projectRow(baseColumnMap, columnMap, newColumnMap);

                columnMap = newColumnMap;
            }

            int enforcedDefaultIndex = table.getIdentityColumnIndex();

            if (enforcedDefaultIndex != -1
                    && ArrayUtil.find(columnMap, enforcedDefaultIndex) > -1)
            {
                if (table.identitySequence.isAlways())
                {
                    if (!overridingUser && !overridingSystem)
                    {
                        throw Error.error(ErrorCode.X_42543);
                    }
                }
                else
                {
                    /*
                                    if (overridingUser) {
                                        throw Trace.error(
                                            Trace.SQL_DEFAULT_CLAUSE_REQUITED);
                                    }
                    */
                }
            }
            else if (overridingUser || overridingSystem)
            {
                unexpectedTokenRequire(Tokens.T_OVERRIDING);
            }

            SqlType[] types = new SqlType[columnMap.Length];

            ArrayUtil.projectRow(baseTable.getColumnTypes(), columnMap, types);

            QueryExpression queryExpression = XreadQueryExpression();

            queryExpression.setAsTopLevel();
            queryExpression.resolve(session, outerRanges, types);

            if (colCount != queryExpression.getColumnCount())
            {
                throw Error.error(ErrorCode.X_42546);
            }

            StatementDMQL cs1 = new StatementInsert(session, table, columnMap,
                                                   columnCheckList,
                                                   queryExpression,
                                                   compileContext);

            return cs1;
        }

        private static void setParameterTypes(Expression tableExpression,
                                              Table table, int[] columnMap)
        {

            for (int i = 0; i < tableExpression.nodes.Length; i++)
            {
                Expression[] list = tableExpression.nodes[i].nodes;

                for (int j = 0; j < list.Length; j++)
                {
                    if (list[j].isParam())
                    {
                        list[j].setAttributesAsColumn(
                            table.getColumn(columnMap[j]), true);
                    }
                }
            }
        }

        /**
         * Creates a DELETE-type Statement from this parse context.
         */
        public StatementDMQL compileDeleteStatement(RangeVariable[] outerRanges)
        {

            Expression condition = null;
            bool truncate = false;
            bool restartIdentity = false;

            switch (token.tokenType)
            {

                case Tokens.TRUNCATE:
                    {
                        read();
                        readThis(Tokens.TABLE);

                        truncate = true;

                        break;
                    }
                case Tokens.DELETE:
                    {
                        read();
                        readThis(Tokens.FROM);

                        break;
                    }
            }

            RangeVariable[] rangeVariables = {
            readSimpleRangeVariable(StatementTypes.DELETE_WHERE) };
            Table table = rangeVariables[0].getTable();
            Table baseTable = table.getBaseTable();

            if (!table.isUpdatable())
            {
                throw Error.error(ErrorCode.X_42000);
            }

            if (truncate)
            {
                switch (token.tokenType)
                {

                    case Tokens.CONTINUE:
                        {
                            read();
                            readThis(Tokens.IDENTITY);

                            break;
                        }
                    case Tokens.RESTART:
                        {
                            read();
                            readThis(Tokens.IDENTITY);

                            restartIdentity = true;

                            break;
                        }
                }

                for (int i = 0; i < table.constraintList.Length; i++)
                {
                    if (table.constraintList[i].getConstraintType()
                            == SchemaObjectConstraintTypes.MAIN)
                    {
                        throw Error.error(ErrorCode.X_23504);
                    }
                }
            }

            if (truncate && table != baseTable)
            {
                throw Error.error(ErrorCode.X_42000);
            }

            if (!truncate && token.tokenType == Tokens.WHERE)
            {
                read();

                condition = XreadBooleanValueExpression();

                EfzList unresolved =
                    condition.resolveColumnReferences(outerRanges, null);

                unresolved = Expression.resolveColumnSet(rangeVariables,
                        unresolved, null);

                ExpressionColumn.checkColumnsResolved(unresolved);
                condition.resolveTypes(session, null);

                if (condition.isParam())
                {
                    condition.dataType = SqlType.SQL_BOOLEAN;
                }

                if (condition.getDataType() != SqlType.SQL_BOOLEAN)
                {
                    throw Error.error(ErrorCode.X_42568);
                }
            }

            if (table != baseTable)
            {
                QuerySpecification select =
                    ((TableDerived)table).getQueryExpression().getMainSelect();

                if (condition != null)
                {
                    condition =
                        condition.replaceColumnReferences(rangeVariables[0],
                                                          select.exprColumns);
                }

                rangeVariables[0] = new RangeVariable(select.rangeVariables[0]);
                condition = ExpressionLogical.andExpressions(select.queryCondition,
                        condition);
            }

            if (condition != null)
            {
                RangeVariableResolver resolver =
                    new RangeVariableResolver(rangeVariables, condition,
                                              compileContext);

                resolver.processConditions();

                rangeVariables = resolver.rangeVariables;
            }

            StatementDMQL cs = new StatementDML(session, table, rangeVariables,
                                                compileContext, restartIdentity);

            return cs;
        }

        /**
         * Creates an UPDATE-type Statement from this parse context.
         */
        public StatementDMQL compileUpdateStatement(RangeVariable[] outerRanges)
        {

            read();

            Expression[] updateExpressions;
            int[] columnMap;
            bool[] columnCheckList;
            OrderedHashSet colNames = new OrderedHashSet();
            EfzArrayList exprList = new EfzArrayList();
            RangeVariable[] rangeVariables = {
            readSimpleRangeVariable(StatementTypes.UPDATE_WHERE) };
            Table table = rangeVariables[0].rangeTable;
            Table baseTable = table.getBaseTable();

            readThis(Tokens.SET);
            readSetClauseList(rangeVariables, colNames, exprList);

            columnMap = table.getColumnIndexes(colNames);
            columnCheckList = table.getColumnCheckList(columnMap);
            updateExpressions = new Expression[exprList.size()];

            exprList.toArray(updateExpressions);

            Expression condition = null;

            if (token.tokenType == Tokens.WHERE)
            {
                read();

                condition = XreadBooleanValueExpression();

                EfzList unresolved =
                    condition.resolveColumnReferences(outerRanges, null);

                unresolved = Expression.resolveColumnSet(rangeVariables,
                        unresolved, null);

                ExpressionColumn.checkColumnsResolved(unresolved);
                condition.resolveTypes(session, null);

                if (condition.isParam())
                {
                    condition.dataType = SqlType.SQL_BOOLEAN;
                }
                else if (condition.getDataType() != SqlType.SQL_BOOLEAN)
                {
                    throw Error.error(ErrorCode.X_42568);
                }
            }

            resolveUpdateExpressions(table, rangeVariables, columnMap,
                                     updateExpressions, outerRanges);

            if (table != baseTable)
            {
                QuerySpecification select =
                    ((TableDerived)table).getQueryExpression().getMainSelect();

                if (condition != null)
                {
                    condition =
                        condition.replaceColumnReferences(rangeVariables[0],
                                                          select.exprColumns);
                }

                rangeVariables[0] = new RangeVariable(select.rangeVariables[0]);
                condition = ExpressionLogical.andExpressions(select.queryCondition,
                        condition);
            }

            if (condition != null)
            {
                RangeVariableResolver resolver =
                    new RangeVariableResolver(rangeVariables, condition,
                                              compileContext);

                resolver.processConditions();

                rangeVariables = resolver.rangeVariables;
            }

            if (table != baseTable)
            {
                int[] baseColumnMap = table.getBaseTableColumnMap();
                int[] newColumnMap = new int[columnMap.Length];

                ArrayUtil.projectRow(baseColumnMap, columnMap, newColumnMap);

                columnMap = newColumnMap;

                for (int i = 0; i < columnMap.Length; i++)
                {
                    if (baseTable.colGenerated[columnMap[i]])
                    {
                        throw Error.error(ErrorCode.X_42513);
                    }
                }
            }

            StatementDMQL cs = new StatementDML(session, table, rangeVariables,
                                                columnMap, updateExpressions,
                                                columnCheckList, compileContext);

            return cs;
        }

        public void resolveUpdateExpressions(Table targetTable,
                                      RangeVariable[] rangeVariables,
                                      int[] columnMap,
                                      Expression[] colExpressions,
                                      RangeVariable[] outerRanges)
        {

            EfzList unresolved = null;
            int enforcedDefaultIndex = -1;

            if (targetTable.hasIdentityColumn()
                    && targetTable.identitySequence.isAlways())
            {
                enforcedDefaultIndex = targetTable.getIdentityColumnIndex();
            }

            for (int i = 0, ix = 0; i < columnMap.Length; ix++)
            {
                Expression expr = colExpressions[ix];
                Expression e;

                // no generated column can be updated
                if (targetTable.colGenerated[columnMap[i]])
                {
                    throw Error.error(ErrorCode.X_42513);
                }

                if (expr.getType() == OpTypes.ROW)
                {
                    Expression[] elements = expr.nodes;

                    for (int j = 0; j < elements.Length; j++, i++)
                    {
                        e = elements[j];

                        if (enforcedDefaultIndex == columnMap[i])
                        {
                            if (e.getType() != OpTypes.DEFAULT)
                            {
                                throw Error.error(ErrorCode.X_42541);
                            }
                        }

                        if (e.isParam())
                        {
                            e.setAttributesAsColumn(
                                targetTable.getColumn(columnMap[i]), true);
                        }
                        else if (e.getType() == OpTypes.DEFAULT)
                        {
                            if (targetTable.colDefaults[columnMap[i]] == null
                                    && targetTable.identityColumn
                                       != columnMap[i])
                            {
                                throw Error.error(ErrorCode.X_42544);
                            }
                        }
                        else
                        {
                            unresolved = expr.resolveColumnReferences(outerRanges,
                                    null);
                            unresolved =
                                Expression.resolveColumnSet(rangeVariables,
                                                            unresolved, null);

                            ExpressionColumn.checkColumnsResolved(unresolved);

                            unresolved = null;

                            e.resolveTypes(session, null);
                        }
                    }
                }
                else if (expr.getType() == OpTypes.TABLE_SUBQUERY)
                {
                    unresolved = expr.resolveColumnReferences(outerRanges, null);
                    unresolved = Expression.resolveColumnSet(rangeVariables,
                            unresolved, null);

                    ExpressionColumn.checkColumnsResolved(unresolved);
                    expr.resolveTypes(session, null);

                    int count = expr.subQuery.queryExpression.getColumnCount();

                    for (int j = 0; j < count; j++, i++)
                    {
                        if (enforcedDefaultIndex == columnMap[i])
                        {
                            throw Error.error(ErrorCode.X_42541);
                        }
                    }
                }
                else
                {
                    e = expr;

                    if (enforcedDefaultIndex == columnMap[i])
                    {
                        if (e.getType() != OpTypes.DEFAULT)
                        {
                            throw Error.error(ErrorCode.X_42541);
                        }
                    }

                    if (e.isParam())
                    {
                        e.setAttributesAsColumn(
                            targetTable.getColumn(columnMap[i]), true);
                    }
                    else if (e.getType() == OpTypes.DEFAULT)
                    {
                        if (targetTable.colDefaults[columnMap[i]] == null
                                && targetTable.identityColumn != columnMap[i])
                        {
                            throw Error.error(ErrorCode.X_42544);
                        }
                    }
                    else
                    {
                        unresolved = expr.resolveColumnReferences(outerRanges,
                                null);
                        unresolved = Expression.resolveColumnSet(rangeVariables,
                                unresolved, null);

                        ExpressionColumn.checkColumnsResolved(unresolved);
                        e.resolveTypes(session, null);
                    }

                    i++;
                }
            }

            if (!targetTable.isView())
            {
                return;
            }

            QuerySpecification select =
                ((TableDerived)targetTable).getQueryExpression().getMainSelect();

            for (int i = 0; i < colExpressions.Length; i++)
            {
                colExpressions[i] =
                    colExpressions[i].replaceColumnReferences(rangeVariables[0],
                        select.exprColumns);
            }
        }

        public void readSetClauseList(RangeVariable[] rangeVars, OrderedHashSet colNames,
                               EfzArrayList expressions)
        {

            while (true)
            {
                int degree;

                if (token.tokenType == Tokens.OPENBRACKET)
                {
                    read();

                    int oldCount = colNames.size();

                    readColumnNames(colNames, rangeVars);

                    degree = colNames.size() - oldCount;

                    readThis(Tokens.CLOSEBRACKET);
                }
                else
                {
                    ColumnSchema column = readColumnName(rangeVars);

                    if (!colNames.add(column.getName().name))
                    {
                        throw Error.error(ErrorCode.X_42578,
                                          column.getName().name);
                    }

                    degree = 1;
                }

                readThis(Tokens.EQUALS);

                int position = getPosition();
                int brackets = readOpenBrackets();

                if (token.tokenType == Tokens.SELECT)
                {
                    rewind(position);

                    SubQuery sq = XreadSubqueryBody(false, OpTypes.ROW_SUBQUERY);

                    if (degree != sq.queryExpression.getColumnCount())
                    {
                        throw Error.error(ErrorCode.X_42546);
                    }

                    Expression e = new Expression(OpTypes.ROW_SUBQUERY, sq);

                    expressions.add(e);

                    if (token.tokenType == Tokens.COMMA)
                    {
                        read();

                        continue;
                    }

                    break;
                }

                if (brackets > 0)
                {
                    rewind(position);
                }

                if (degree > 1)
                {
                    readThis(Tokens.OPENBRACKET);

                    Expression e = readRow();

                    readThis(Tokens.CLOSEBRACKET);

                    int rowDegree = e.getType() == OpTypes.ROW ? e.nodes.Length
                                                               : 1;

                    if (degree != rowDegree)
                    {
                        throw Error.error(ErrorCode.X_42546);
                    }

                    expressions.add(e);
                }
                else
                {
                    Expression e = XreadValueExpressionWithContext();

                    expressions.add(e);
                }

                if (token.tokenType == Tokens.COMMA)
                {
                    read();

                    continue;
                }

                break;
            }
        }

        /**
         * Retrieves a MERGE Statement from this parse context.
         */
        public StatementDMQL compileMergeStatement(RangeVariable[] outerRanges)
        {

            bool[] insertColumnCheckList;
            int[] insertColumnMap = null;
            int[] updateColumnMap = null;
            int[] baseUpdateColumnMap;
            Table table;
            RangeVariable targetRange;
            RangeVariable sourceRange;
            Expression mergeCondition;
            EfzArrayList updateList = new EfzArrayList();
            Expression[] updateExpressions = null;
            EfzArrayList insertList = new EfzArrayList();
            Expression insertExpression = null;

            read();
            readThis(Tokens.INTO);

            targetRange = readSimpleRangeVariable(StatementTypes.MERGE);
            table = targetRange.rangeTable;

            readThis(Tokens.USING);

            sourceRange = readTableOrSubquery();

            // parse ON search conditions
            readThis(Tokens.ON);

            mergeCondition = XreadBooleanValueExpression();

            if (mergeCondition.getDataType() != SqlType.SQL_BOOLEAN)
            {
                throw Error.error(ErrorCode.X_42568);
            }

            RangeVariable[] fullRangeVars = new RangeVariable[] {
            sourceRange, targetRange
        };
            RangeVariable[] sourceRangeVars = new RangeVariable[] { sourceRange };
            RangeVariable[] targetRangeVars = new RangeVariable[] { targetRange };

            // parse WHEN clause(s) and convert lists to arrays
            insertColumnMap = table.getColumnMap();
            insertColumnCheckList = table.getNewColumnCheckList();

            OrderedHashSet updateColNames = new OrderedHashSet();
            OrderedHashSet insertColNames = new OrderedHashSet();

            readMergeWhen(insertColNames, updateColNames, insertList, updateList,
                          targetRangeVars, sourceRange);

            if (insertList.size() > 0)
            {
                int colCount = insertColNames.size();

                if (colCount != 0)
                {
                    insertColumnMap = table.getColumnIndexes(insertColNames);
                    insertColumnCheckList =
                        table.getColumnCheckList(insertColumnMap);
                }

                insertExpression = (Expression)insertList.get(0);

                setParameterTypes(insertExpression, table, insertColumnMap);
            }

            if (updateList.size() > 0)
            {
                updateExpressions = new Expression[updateList.size()];

                updateList.toArray(updateExpressions);

                updateColumnMap = table.getColumnIndexes(updateColNames);
            }

            if (updateExpressions != null)
            {
                Table baseTable = table.getBaseTable();

                baseUpdateColumnMap = updateColumnMap;

                if (table != baseTable)
                {
                    baseUpdateColumnMap = new int[updateColumnMap.Length];

                    ArrayUtil.projectRow(table.getBaseTableColumnMap(),
                                         updateColumnMap, baseUpdateColumnMap);
                }

                resolveUpdateExpressions(table, sourceRangeVars, updateColumnMap,
                                         updateExpressions, outerRanges);
            }

            EfzList unresolved = null;

            unresolved = mergeCondition.resolveColumnReferences(fullRangeVars,
                    null);

            ExpressionColumn.checkColumnsResolved(unresolved);
            mergeCondition.resolveTypes(session, null);

            if (mergeCondition.isParam())
            {
                mergeCondition.dataType = SqlType.SQL_BOOLEAN;
            }

            if (mergeCondition.getDataType() != SqlType.SQL_BOOLEAN)
            {
                throw Error.error(ErrorCode.X_42568);
            }

            RangeVariableResolver resolver =
                new RangeVariableResolver(fullRangeVars, mergeCondition,
                                          compileContext);

            resolver.processConditions();

            fullRangeVars = resolver.rangeVariables;

            if (insertExpression != null)
            {
                unresolved =
                    insertExpression.resolveColumnReferences(sourceRangeVars,
                        unresolved);

                ExpressionColumn.checkColumnsResolved(unresolved);
                insertExpression.resolveTypes(session, null);
            }

            StatementDMQL cs = new StatementDML(session, fullRangeVars,
                                                insertColumnMap, updateColumnMap,
                                                insertColumnCheckList,
                                                mergeCondition, insertExpression,
                                                updateExpressions, compileContext);

            return cs;
        }

        /**
         * Parses a WHEN clause from a MERGE statement. This can be either a
         * WHEN MATCHED or WHEN NOT MATCHED clause, or both, and the appropriate
         * values will be updated.
         *
         * If the var that is to hold the data is not null, then we already
         * encountered this type of clause, which is only allowed once, and at least
         * one is required.
         */
        private void readMergeWhen(OrderedHashSet insertColumnNames,
                                   OrderedHashSet updateColumnNames,
                                   EfzArrayList insertExpressions,
                                   EfzArrayList updateExpressions,
                                   RangeVariable[] targetRangeVars,
                                   RangeVariable sourceRangeVar)
        {

            Table table = targetRangeVars[0].rangeTable;
            int columnCount = table.getColumnCount();

            readThis(Tokens.WHEN);

            if (token.tokenType == Tokens.MATCHED)
            {
                if (updateExpressions.size() != 0)
                {
                    throw Error.error(ErrorCode.X_42547);
                }

                read();
                readThis(Tokens.THEN);
                readThis(Tokens.UPDATE);
                readThis(Tokens.SET);
                readSetClauseList(targetRangeVars, updateColumnNames,
                                  updateExpressions);
            }
            else if (token.tokenType == Tokens.NOT)
            {
                if (insertExpressions.size() != 0)
                {
                    throw Error.error(ErrorCode.X_42548);
                }

                read();
                readThis(Tokens.MATCHED);
                readThis(Tokens.THEN);
                readThis(Tokens.INSERT);

                // parse INSERT statement
                // optional column list
                int brackets = readOpenBrackets();

                if (brackets == 1)
                {
                    readSimpleColumnNames(insertColumnNames, targetRangeVars[0]);
                    readThis(Tokens.CLOSEBRACKET);

                    brackets = 0;
                }

                readThis(Tokens.VALUES);

                Expression e = XreadContextuallyTypedTable(columnCount);

                if (e.nodes.Length != 1)
                {
                    throw Error.error(ErrorCode.X_21000);
                }

                insertExpressions.add(e);
            }
            else
            {
                throw unexpectedToken();
            }

            if (token.tokenType == Tokens.WHEN)
            {
                readMergeWhen(insertColumnNames, updateColumnNames,
                              insertExpressions, updateExpressions,
                              targetRangeVars, sourceRangeVar);
            }
        }

        /**
         * Retrieves a CALL Statement from this parse context.
         */

        // to do call argument name and type resolution
        public StatementDMQL compileCallStatement(RangeVariable[] outerRanges,
                                           bool isStrictlyProcedure)
        {

            read();

            if (isIdentifier())
            {
                checkValidCatalogName(token.namePrePrefix);

                RoutineSchema routineSchema =
                    (RoutineSchema)database.schemaManager.findSchemaObject(
                        token.tokenString,
                        session.getSchemaName(token.namePrefix),
                        SchemaObjectTypes.PROCEDURE);

                if (routineSchema != null)
                {
                    read();

                    EfzArrayList list = new EfzArrayList();

                    readThis(Tokens.OPENBRACKET);

                    if (token.tokenType == Tokens.CLOSEBRACKET)
                    {
                        read();
                    }
                    else
                    {
                        while (true)
                        {
                            Expression e = XreadValueExpression();

                            list.add(e);

                            if (token.tokenType == Tokens.COMMA)
                            {
                                read();
                            }
                            else
                            {
                                readThis(Tokens.CLOSEBRACKET);

                                break;
                            }
                        }
                    }

                    Expression[] arguments = new Expression[list.size()];

                    list.toArray(arguments);

                    Routine routine =
                        routineSchema.getSpecificRoutine(arguments.Length);
                    EfzList unresolved = null;

                    for (int i = 0; i < arguments.Length; i++)
                    {
                        Expression e = arguments[i];

                        if (e.isParam())
                        {
                            e.setAttributesAsColumn(
                                routine.getParameter(i),
                                routine.getParameter(i).isWriteable());
                        }
                        else
                        {
                            int paramMode =
                                routine.getParameter(i).getParameterMode();

                            unresolved =
                                arguments[i].resolveColumnReferences(outerRanges,
                                    unresolved);

                            if (paramMode
                                    != SchemaObjectParameterModes.PARAM_IN)
                            {
                                if (e.getType() != OpTypes.VARIABLE)
                                {
                                    throw Error.error(ErrorCode.X_42603);
                                }
                            }
                        }
                    }

                    ExpressionColumn.checkColumnsResolved(unresolved);

                    for (int i = 0; i < arguments.Length; i++)
                    {
                        arguments[i].resolveTypes(session, null);
                    }

                    StatementDMQL cs = new StatementProcedure(session, routine,
                        arguments, compileContext);

                    return cs;
                }
            }

            if (isStrictlyProcedure)
            {
                throw Error.error(ErrorCode.X_42501, token.tokenString);
            }

            Expression expression = this.XreadValueExpression();
            EfzList unresolved2 = expression.resolveColumnReferences(outerRanges,
                null);

            ExpressionColumn.checkColumnsResolved(unresolved2);
            expression.resolveTypes(session, null);

            //        expression.paramMode = PARAM_OUT;
            StatementDMQL cs2 = new StatementProcedure(session, expression,
                compileContext);

            return cs2;
        }
    }
}
